﻿using System;
using System.Collections.Generic;
using Bookvar.ObjectModel.Actions;
using Bookvar.ServiceModel;

namespace Bookvar.UI
{
    /// <summary>
    /// This class represents a basic action service. It just executes all the actions and stores them 
    /// for undo/redo.
    /// </summary>
    public class ActionService : IActionService
    {
        private readonly Stack<IAction> redoStack;
        private readonly Stack<IAction> undoStack;

        ///<summary>
        ///
        ///</summary>
        public ActionService()
        {
            undoStack = new Stack<IAction>();
            redoStack = new Stack<IAction>();
        }

        #region IActionService Members

        /// <summary>
        /// This is always true because this default action service support undo/redo operations.
        /// </summary>
        /// <value>
        /// 	<c>true</c> because this default action service support undo/redo operations.
        /// </value>
        public bool CanUndo
        {
            get { return true; }
        }

        /// <summary>
        /// This is always true because this default action service support undo/redo operations.
        /// </summary>
        /// <value>
        /// 	<c>true</c> because this default action service support undo/redo operations.
        /// </value>
        public bool CanRedo
        {
            get { return true; }
        }

        /// <summary>
        /// Gets a value indicating whether this action service has an action to undo.
        /// This is true when any action that can be undone is executed and the action service
        /// supports undo operations.
        /// </summary>
        /// <value>
        /// 	<c>true</c> if this action service has an action to undo; otherwise, <c>false</c>.
        /// </value>
        public bool HasActionToUndo
        {
            get { return undoStack.Count > 0; }
        }

        /// <summary>
        /// Gets a value indicating whether this action service has an action to redo.
        /// This is true when at least one action is undone and the action service supports redo.
        /// </summary>
        /// <value>
        /// 	<c>true</c> if this action service has an action to redo; otherwise, <c>false</c>.
        /// </value>
        public bool HasActionToRedo
        {
            get { return redoStack.Count > 0; }
        }

        /// <summary>
        /// Redoes the last executed action.
        /// </summary>
        public void Redo()
        {
            if ( redoStack.Count <= 0 )
            {
                return;
            }

            IAction action = redoStack.Pop();

            if ( action.CanRedo )
            {
                action.Execute();
                if ( action.CanUndo )
                {
                    undoStack.Push( action );
                }
            }
        }

        /// <summary>
        /// Undoes the last executed action.
        /// </summary>
        public void Undo()
        {
            if ( undoStack.Count <= 0 )
            {
                return;
            }

            IAction action = undoStack.Pop();

            if ( action.CanUndo )
            {
                action.Undo();
                if ( action.CanRedo )
                {
                    redoStack.Push( action );
                }
                else
                {
                    action.Dispose();
                }
            }
        }

        /// <summary>
        /// Executes the action.
        /// </summary>
        /// <remarks>
        /// Execute clears the actions for redo as they are no longer valid in the new context.
        /// </remarks>
        /// <exception cref="ArgumentNullException">
        /// if <paramref name="action"/> is null.
        /// </exception>
        /// <param name="action">The action to be executed.</param>
        public void Execute( IAction action )
        {
            if ( action == null )
            {
                throw new ArgumentNullException( "action" );
            }

            ClearRedoStack();

            action.Execute();
            if ( action.CanUndo )
            {
                undoStack.Push( action );
            }
            else
            {
                action.Dispose();
            }
        }

        #endregion

        private void ClearRedoStack()
        {
            while ( redoStack.Count > 0 )
            {
                IAction toClear = redoStack.Pop();
                toClear.Dispose();
            }
        }
    }
}